package logs

import (
	"fmt"
	"github.com/natefinch/lumberjack"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"os"
	"strings"
	"time"
)

var (
	zapLogger *zap.Logger
)

type Setting struct {
	// RuntimeRootPath 根路径，默认是/home/service/
	RuntimeRootPath string `mapstructure:"runtime_root_path"`
	// Project 当前所在ip
	LocalIp string
	// Project 项目名，用于组成最终的日志路径
	Project string
	Module  string
	// 日志文件最多保留时长，天
	MaxRetentionDays int
	// 是否日志分级存储
	IsLevel bool
	// 是否标准输出到控制台
	StdOut bool
}

// InitLog 初始化全局logger
// 日志路径一般为：/home/service/${SERVICE_NAME}/logs
func InitLog(setting Setting) {
	// check
	if setting.RuntimeRootPath == "" {
		setting.RuntimeRootPath = "/data/logs/"
	}
	if strings.HasSuffix(setting.RuntimeRootPath, "/") == false {
		setting.RuntimeRootPath = setting.RuntimeRootPath + "/"
	}
	if setting.LocalIp == "" {
		setting.LocalIp = "127.0.0.1"
	}
	if setting.Project == "" {
		setting.Project = "test_project"
	}

	if setting.MaxRetentionDays == 0 {
		setting.MaxRetentionDays = 5
	}
	// 分级别存储文件
	// debug-level用于打印accessLog， 其他的业务正常日志采用info-level
	debugLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
		return lvl == zapcore.DebugLevel
	})

	infoLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
		return lvl == zapcore.InfoLevel
	})

	warnLevel := zap.LevelEnablerFunc(func(lev zapcore.Level) bool {
		return lev == zap.WarnLevel
	})

	errorLevel := zap.LevelEnablerFunc(func(lev zapcore.Level) bool {
		return lev > zap.WarnLevel
	})

	debugCore := getLevelCore(debugLevel, "debug", setting)
	infoCore := getLevelCore(infoLevel, "info", setting)
	warnCore := getLevelCore(warnLevel, "warn", setting)
	errorCore := getLevelCore(errorLevel, "error", setting)

	zapLogger = zap.New(zapcore.NewTee(infoCore, errorCore, warnCore, debugCore))
}

//
// AccessLog
//  @Description: 封装debug级别的日志，用于打印accesslog日志。
//  @param msg
//  @param fields
//
func AccessLog(msg string, fields ...zap.Field) {
	GetLogger().Debug(msg, fields...)
}

//
// LatencyLog
//  @Description:  封装warn级别的日志， 用于打印 服务耗时的日志（包含http和数据库）
//  @param msg
//  @param fields
//
func LatencyLog(msg string, fields ...zap.Field) {
	GetLogger().Warn(msg, fields...)
}

//
// ErrorLog
//  @Description:  封装error级别的日志， 可以是Fatal，Panic
//  @param msg
//  @param fields
//
func ErrorLog(msg string, fields ...zap.Field) {
	GetLogger().Error(msg, fields...)
}

//
// GetLogger
//  @Description: 获取logger实例 （业务中不使用debug,warn level来打印业务日志）
//  @return *zap.Logger
//
func GetLogger() *zap.Logger {
	return zapLogger
}

func getLevelCore(enablerFunc zap.LevelEnablerFunc, levelName string, setting Setting) (core zapcore.Core) {

	// 优先使用MaxBackups保存。但是如果配置了MaxAge，那么还会经由MaxAge条件再过滤一次，即MaxAge决定最终要保存多少个
	hook := &lumberjack.Logger{
		Filename:   GetLogFilePath(setting) + getLogFileName(levelName), // 日志文件路径
		MaxSize:    512,                                                 // 每个日志文件保存的最大尺寸 单位：M
		MaxBackups: setting.MaxRetentionDays,                            // 日志文件最多保存多少个备份
		MaxAge:     setting.MaxRetentionDays,                            // 文件最多保存多少天
		Compress:   false,                                               // 是否压缩
	}

	if setting.StdOut {
		core = zapcore.NewCore(
			zapcore.NewConsoleEncoder(getEncoderConfig(setting)),                           // 编码器配置
			zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stdout), zapcore.AddSync(hook)), // 打印到控制台和文件
			enablerFunc, // 日志级别
		)
	} else {
		core = zapcore.NewCore(
			zapcore.NewConsoleEncoder(getEncoderConfig(setting)), // 编码器配置
			zapcore.NewMultiWriteSyncer(zapcore.AddSync(hook)),
			enablerFunc, // 日志级别
		)
	}
	return core
}

//
// getEncoderConfig
//  @Description:  自定义日志格式 zap配置
//  @return zapcore.EncoderConfig
//
func getEncoderConfig(setting Setting) zapcore.EncoderConfig {
	encoderConfig := zap.NewProductionEncoderConfig()

	// 自定义时间输出格式
	customTimeEncoder := func(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
		enc.AppendString(t.Format("2006-01-02 15:04:05"))
	}
	// 自定义日志级别显示
	customLevelEncoder := func(level zapcore.Level, enc zapcore.PrimitiveArrayEncoder) {

		// 定义debug级别为access日志
		if level == zap.DebugLevel {
			enc.AppendString("ACCESS")
		} else {
			enc.AppendString(level.CapitalString())
		}

		enc.AppendString(setting.LocalIp)
		enc.AppendInt(0)
	}

	encoderConfig.EncodeTime = customTimeEncoder
	encoderConfig.EncodeLevel = customLevelEncoder
	// 分隔字符
	encoderConfig.ConsoleSeparator = "|"
	return encoderConfig
}

func GetLogFilePath(setting Setting) string {

	return fmt.Sprintf("%s%s/logs/", setting.RuntimeRootPath, setting.Project)
}

// getLogFileName 获取日志文件名
// 三类日志，级别为warn的写入slow日志，级别为debug的写入access日志，级别为error的写入error日志
func getLogFileName(level string) string {

	var filename string
	if level == "debug" {
		filename = "access"
	} else if level == "warn" {
		filename = "slow"
	} else {
		filename = "error"
	}

	return fmt.Sprintf("%s.log", filename)
}
